package com.xsjiong.jaript.lexer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;

public class JLexer {
	private String Source;
	private int LastMark = 0;
	private int I = 0;
	private char C;
	private boolean EOF = false;
	private int Length;

	public JLexer(InputStream in) throws IOException {
		this(in, Charset.defaultCharset());
	}

	public JLexer(InputStream in, String charsetName) throws IOException {
		this(in, Charset.forName(charsetName));
	}

	public JLexer(InputStream in, Charset charset) throws IOException {
		byte[] buf = new byte[1024];
		int read;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		while ((read = in.read(buf)) != -1) out.write(buf, 0, read);
		in.close();
		out.close();
		Source = new String(out.toByteArray(), charset);
		Length = Source.length();
	}

	public JLexer(Reader reader) throws IOException {
		char[] buf = new char[512];
		int read;
		StringBuffer s = new StringBuffer();
		while ((read = reader.read(buf)) != -1) s.append(buf, 0, read);
		reader.close();
		buf = null;
		Source = s.toString();
		Length = Source.length();
		s.setLength(0);
		s = null;
	}

	public JLexer(String s) {
		this.Source = s;
		Length = Source.length();
	}

	private void G() {
		if (I == Length) {
			EOF = true;
			C = ' ';
			I++;
			return;
		}
		C = Source.charAt(I++);
	}

	public String getToken() {
		return Source.substring(LastMark, I);
	}

	public String getSource() {
		return Source;
	}

	private boolean isWhitespace() {
		return /*C != '\n' && */Character.isWhitespace(C);
	}

	private void ReadSpaces() {
		do {
			G();
		} while (!EOF && isWhitespace());
		I--;
	}

	private boolean isLetter() {
		return (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z');
	}

	private boolean isDigit() {
		return C >= '0' && C <= '9';
	}

	public boolean isEOF() {
		return EOF;
	}

	private boolean isIdentifierStart() {
		return Character.isJavaIdentifierStart(C);
	}

	private boolean isIdentifierPart() {
		return Character.isJavaIdentifierPart(C);
	}

	public byte ReadSymbol() throws JLexerException {
		if (EOF) return JSymbols.EOF;
		ReadSpaces();
		LastMark = I;
		if (EOF) return JSymbols.EOF;
		G();
		if (isIdentifierStart()) {
			do {
				G();
			} while (isIdentifierPart() && !EOF);
			I--;
			byte key = JSymbols._GetKeywordIndex(getToken());
			if (key != -128) return key;
			return JSymbols.Identifier;
		} else if (isDigit()) {
			do {
				G();
			} while (isDigit() && !EOF);
			I--;
			if (isLetter())
				throw new JLexerException(JLexerException.INVALID_IDENTIFIER, I);
			else
				return JSymbols.Integer;
		}
		if (C == '/') {
			G();
			if (C == '/') { // 比如 ‘// Comment'
				do {
					G();
				} while (C != '\n' && !EOF);
				if (C == '\n') I--;
				// 跳过注释
				ReadSpaces();
				LastMark = I;
				G();
			} else if (C == '*') {
				do {
					G();
					if (C == '*' && !EOF) {
						G();
						if (C == '/')
							break;
					}
				} while (!EOF);
				ReadSpaces();
				LastMark = I;
				G();
			} else if (C == '=') return JSymbols.SelfDivision; else {
				I--;
				return JSymbols.Division;
			}
		}
		// TODO '::' 运算符 & '...?...:...' 运算符
		switch (C) {
			// ------------
			// 运算符
			// ------------
			case '+': {
				G();
				switch (C) {
					case '+':
						return JSymbols.Increase;
					case '=':
						return JSymbols.SelfPlus;
					default:
						I--;
						return JSymbols.Plus;
				}
			}
			case '-': {
				G();
				switch (C) {
					case '-':
						return JSymbols.Decrease;
					case '=':
						return JSymbols.SelfMinus;
					default:
						I--;
						return JSymbols.Minus;
				}
			}
			case '*': {
				G();
				if (C == '=')
					return JSymbols.SelfMultiplication;
				I--;
				return JSymbols.Multiplication;
			}
			case '=': {
				G();
				if (C == '=')
					return JSymbols.Equal;
				I--;
				return JSymbols.Assignment;
			}
			case '>': {
				G();
				if (C == '=')
					return JSymbols.GreaterOrEqual;
				else if (C == '>') {
					G();
					if (C == '=')
						return JSymbols.SelfShiftBitsRight;
					else if (C == '>') {
						G();
						if (C == '=') return JSymbols.SelfUnsignedShiftBitsRight;
						I--;
						return JSymbols.UnsignedShiftBitsRight;
					} else {
						I--;
						return JSymbols.ShiftBitsRight;
					}
				}
				I--;
				return JSymbols.Greater;
			}
			case '<': {
				G();
				if (C == '=')
					return JSymbols.LessOrEqual;
				else if (C == '<') {
					G();
					if (C == '=')
						return JSymbols.SelfShiftBitsLeft;
					I--;
					return JSymbols.ShiftBitsLeft;
				}
				I--;
				return JSymbols.Less;
			}
			case '&': {
				G();
				if (C == '&')
					return JSymbols.And;
				else if (C == '=')
					return JSymbols.SelfBitwiseAnd;
				I--;
				return JSymbols.BitwiseAnd;
			}
			case '|': {
				G();
				if (C == '|')
					return JSymbols.Or;
				else if (C == '=')
					return JSymbols.SelfBitwiseOr;
				I--;
				return JSymbols.BitwiseOr;
			}
			case '!': {
				G();
				if (C == '=')
					return JSymbols.NotEqualTo;
				I--;
				return JSymbols.Not;
			}
			case '%': {
				G();
				if (C == '=')
					return JSymbols.SelfModulo;
				I--;
				return JSymbols.Modulo;
			}
			case '^': {
				G();
				if (C == '=')
					return JSymbols.SelfBitwiseXor;
				I--;
				return JSymbols.BitwiseXor;
			}
			case '~':
				return JSymbols.BitInversion;
			// ------------
			// 分界符
			// ------------
			case '(':
				return JSymbols.LeftParenthesis;
			case ')':
				return JSymbols.RightParenthesis;
			case '{':
				return JSymbols.LeftBrace;
			case '}':
				return JSymbols.RightBrace;
			case '[':
				return JSymbols.LeftSquareBracket;
			case ']':
				return JSymbols.RightSquareBracket;
			case ';':
				return JSymbols.Semicolon;
			case ':':
				return JSymbols.Colon;
			case '?':
				return JSymbols.QuestionMark;
			case '.': {
				if (Source.charAt(I) == '.' && Source.charAt(I + 1) == '.') {
					I += 2;
					return JSymbols.VariableArguments;
				}
				return JSymbols.Period;
			}
			/*case '\n':
				return JSymbols.LineSeparator;*/
			case ',':
				return JSymbols.Comma;
			case '"': {
				StringTmp.setLength(0);
				boolean zy = false;
				do {
					G();
					if (EOF) return JSymbols.String;
					if (C == '\\') {
						if (zy) {
							StringTmp.append('\\');
							zy = false;
						} else zy = true;
					} else {
						if (zy) {
							if (isDigit()) {
								int tmp = (C - '0');
								if (tmp > 7)
									throw new JLexerException(JLexerException.ILLEGAL_ESCAPE_CHAR, I);
								do {
									G();
									if (C < '0' || C > '7') {
										I--;
										break;
									}
									tmp = (tmp << 3) + (C - '0');
									if (tmp > 255) {
										I--;
										tmp >>= 3;
										break;
									}
								} while (true);
								StringTmp.append((char) tmp);
							} else if (C == 'u') {
								int tmp = (C - '0');
								if (tmp > 7)
									throw new JLexerException(JLexerException.ILLEGAL_ESCAPE_CHAR, I, (int) C);
								int hex;
								do {
									G();
									hex = HexToInt(C);
									if (hex == -1) {
										I--;
										break;
									}
									tmp = (tmp << 4) + hex;
									if (tmp > 65535) {
										I--;
										tmp >>= 4;
										break;
									}
								} while (true);
								StringTmp.append((char) tmp);
							}
							zy = false;
							switch (C) {
								case '\'':
									StringTmp.append('\'');
									break;
								case '"':
									StringTmp.append('"');
									break;
								case 'r':
									StringTmp.append('\r');
									break;
								case 'n':
									StringTmp.append('\n');
									break;
								case 'f':
									StringTmp.append('\f');
									break;
								case 't':
									StringTmp.append('\t');
									break;
								case 'b':
									StringTmp.append('\b');
									break;
								default: {
									throw new JLexerException(JLexerException.ILLEGAL_ESCAPE_CHAR, I, (int) C);
								}
							}
						} else {
							if (C == '"') break;
							else StringTmp.append(C);
						}
					}
				} while (true);
				return JSymbols.String;
			}
		}
		throw new JLexerException(JLexerException.UNRECOGNIZED_CHAR, I, (int) C);
	}

	private StringBuffer StringTmp = new StringBuffer();

	private static int HexToInt(char c) {
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'A' && c <= 'F')
			return c - 'A' + 10;
		return -1;
	}

	public int getIndex() {
		return I;
	}

	public String getString() {
		return StringTmp.toString();
	}

	public void gotoLast() {
		I = LastMark;
	}
}
